﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClearingGraphAutomaton.Base
{
    /// <summary>
    /// Base cass representing the 2D graph.
    /// </summary>
    partial class Graph
    {
        /// <summary>
        /// The type of the graph.
        /// </summary>
        public enum GraphType
        {
            /// <summary>
            /// Unspecified type of the graph.
            /// </summary>
            None,

            /// <summary>
            /// Graph with horizontal and vertical edges only.
            /// </summary>
            Normal,

            /// <summary>
            /// Graph with horizontal, vertical and diagonal edges.
            /// </summary>
            Diagonal
        }

        /// <summary>
        /// Creates the instance of the 2D graph.
        /// </summary>
        /// <param name="rows">Number of rows of the graph.</param>
        /// <param name="cols">Number of columns of the graph.</param>
        /// <param name="type">Type of the graph.</param>
        public Graph(int rows, int cols, GraphType type)
        {
            this.Rows = rows;
            this.Columns = cols;
            this.nodes = new Node[rows, cols];
            for (int row = 0; row < rows; ++row)
            {
                for (int col = 0; col < cols; ++col)
                {
                    this.nodes[row, col] = new Node(row, col);
                }
            }

            int edgesCount = 0;
            int[] edgesOffsetRow = null;
            int[] edgesOffsetCol = null;
            Edge.EdgeOrientation[] edgesOrientation = null;
            switch (type)
            {
                case GraphType.None: break;
                case GraphType.Normal:
                    edgesCount = NormalEdgesCount;
                    edgesOffsetRow = NormalEdgesOffsetRow;
                    edgesOffsetCol = NormalEdgesOffsetCol;
                    edgesOrientation = NormalEdgesOrientation;
                    break;
                case GraphType.Diagonal:
                    edgesCount = DiagonalEdgesCount;
                    edgesOffsetRow = DiagonalEdgesOffsetRow;
                    edgesOffsetCol = DiagonalEdgesOffsetCol;
                    edgesOrientation = DiagonalEdgesOrientation;
                    break;
            }

            if (edgesCount > 0)
            {
                for (int row = 0; row < rows; ++row)
                {
                    for (int col = 0; col < cols; ++col)
                    {
                        Node fromNode = this.nodes[row, col];
                        for (int edge = 0; edge < edgesCount; ++edge)
                        {
                            int toRow = row + edgesOffsetRow[edge];
                            int toCol = col + edgesOffsetCol[edge];
                            Edge.EdgeOrientation orientation = edgesOrientation[edge];
                            if (toRow >= 0 && toRow < rows &&
                                toCol >= 0 && toCol < cols)
                            {
                                Node toNode = this.nodes[toRow, toCol];
                                fromNode.Edges.Add(new Edge(fromNode, toNode, orientation));
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Number of rows.
        /// </summary>
        public int Rows { get; protected set; }

        /// <summary>
        /// Number of columns.
        /// </summary>
        public int Columns { get; protected set; }

        /// <summary>
        /// Gets the node at the specified row and column.
        /// </summary>
        /// <param name="row">Row of the node.</param>
        /// <param name="col">Column of the node.</param>
        /// <returns>Returns the node at the specified row and column.</returns>
        public Node this[int row, int col]
        {
            get
            {
                return this.nodes[row, col];
            }
        }

        /// <summary>
        /// Clears the node of the graph a the given [row, col] position.
        /// </summary>
        /// <param name="row">Row of the node.</param>
        /// <param name="col">Column of the node.</param>
        public void ClearNode(int row, int col)
        {
            Node node = this.nodes[row, col];
            if (node != null && node.Edges.Count > 0)
            {
                Dictionary<Edge.EdgeOrientation, Node> nodeConnections = new Dictionary<Edge.EdgeOrientation, Node>(node.Edges.Count);
                List<Node> repairNodeList = new List<Node>(node.Edges.Count);
                foreach (Edge edge in node.Edges)
                {
                    nodeConnections[edge.Orientation] = edge.ToNode;
                    repairNodeList.Add(edge.ToNode);
                }
                foreach (Node repairNode in repairNodeList)
                {
                    foreach (Edge edge in repairNode.Edges)
                    {
                        if (edge.ToNode == node)
                        {
                            edge.ToNode = nodeConnections[edge.Orientation];
                        }
                    }
                }
            }
            this.nodes[node.Row, node.Col] = null;
        }

        private Node[,] nodes;

        private static int NormalEdgesCount = 4;
        private static int[] NormalEdgesOffsetRow = new int[] { -1, 0, +1, 0 };
        private static int[] NormalEdgesOffsetCol = new int[] { 0, +1, 0, -1 };
        private static Edge.EdgeOrientation[] NormalEdgesOrientation = new Edge.EdgeOrientation[] 
        { 
            Edge.EdgeOrientation.Up, 
            Edge.EdgeOrientation.Right, 
            Edge.EdgeOrientation.Down, 
            Edge.EdgeOrientation.Left 
        };

        private static int DiagonalEdgesCount = 8;
        private static int[] DiagonalEdgesOffsetRow = new int[] { -1, -1, 0, +1, +1, +1, 0, -1 };
        private static int[] DiagonalEdgesOffsetCol = new int[] { 0, +1, +1, +1, 0, -1, -1, -1 };
        private static Edge.EdgeOrientation[] DiagonalEdgesOrientation = new Edge.EdgeOrientation[] 
        { 
            Edge.EdgeOrientation.Up,
            Edge.EdgeOrientation.UpRight,
            Edge.EdgeOrientation.Right,
            Edge.EdgeOrientation.DownRight,
            Edge.EdgeOrientation.Down,
            Edge.EdgeOrientation.DownLeft,
            Edge.EdgeOrientation.Left,
            Edge.EdgeOrientation.UpLeft
        };
    }
}
